#include "Job.h"
#include "Utils.h"

#include "../logger/ScopedWriter.h"

#include <QFile>

namespace Client {

Job::Job() : mutex(1) {
	status = jsPending;
	seeds_count = 0;
	chunks_done = 0;

	disk_file = 0;

	age = 0;
}

Job::~Job() {
	if (disk_file) {
		disk_file->close();
		delete disk_file;
	}
}

int Job::getNextFreeChunk(int locker) {
	mutex.p();
	int ret = -1;

	for (int i = 0; i <chunks_count; ++i)
		if ( (!chunks.contains(i)) && (!locked.values().contains(i)) ) {
			ret = i;
			break;
		}

	// blokujemy wybrany fragment
	if ( (ret >= 0) && (locker > 0) )
		locked.insert(locker, ret);

	mutex.v();
	return ret;
}

bool Job::addChunk(int chunk, QByteArray chunk_data, int seed_id) {
	ScopedWriter sw ("Job::addChunk()", true);

	mutex.p();

	if (disk_file == NULL) {
		disk_file = new QFile(QString("./downloads/") + fname);
		disk_file->open(QFile::WriteOnly);
		disk_file->resize(size);
		disk_file->reset();
	}

	// ustawiamy klienta w pozycji niepołączonej
	for (int i = 0; i < seeds.size(); ++i) {
		if (seeds[i].id == seed_id) {
			//sw << "Jest seed... odklejam go\n";
			if (seeds[i].connected) {
				seeds[i].connected = false;
				seeds_count--;
			}
		}
	}

	if (chunks.contains(chunk)) {
		mutex.v();
		sw << "Fragment został już wcześniej pobrany. Ignoruję...\n";
		return false;
	}

	// rozmiar fragmentu się nie zgadza
	if ( (chunk_data.size() != chunk_size) && (chunk < chunks_count-1) ) {
		mutex.v();
		sw << "Rozmiar fragmentu się nie zgadza. Ignoruję...\n";
		return false;
	}

	chunks.push_back(chunk);
	chunks_done++;

	locked.remove(seed_id);

	disk_file->reset();
	disk_file->seek(chunk_size*chunk);
	disk_file->write(chunk_data.data(), chunk_data.size());

	if (chunks_done >= chunks_count) {
		//sw << "Pobrano ostatni fragment pliku. Obliczam sume kontrolną... ";
		status = jsFinished;

		disk_file->close();
		delete disk_file;
		disk_file = NULL;

		QByteArray md5 = Utils::md5sum(QString("./downloads/") + fname);
		if (md5 != hash) {
			/// @todo usunąć plik z folderu
			sw << "Pliki się różnią. Usuwam pobrany plik.\n";
		} else {
			/// @todo dodać plik do udostępniania
			sw << "Plik pobrano bez błędów.\n";
		}
	}

	mutex.v();

	return true;
}


/*bool Job::addChunk(int chunk, QByteArray chunk_data, int seed_id) {
	ScopedWriter sw ("Job::addChunk()", true);

	mutex.p();

//	if (disk_file == NULL) {
//		disk_file = new QFile(QString("./downloads/") + fname);
//		disk_file->open(QFile::WriteOnly);
//		disk_file->resize(size);
//		disk_file->reset();
//	}

	// ustawiamy klienta w pozycji niepołączonej
	for (int i = 0; i < seeds.size(); ++i) {
		if (seeds[i].id == seed_id) {
			//sw << "Jest seed... odklejam go\n";
			if (seeds[i].connected) {
				seeds[i].connected = false;
				seeds_count--;
			}
		}
	}

	if (chunks.contains(chunk)) {
		mutex.v();
		sw << "Fragment został już wcześniej pobrany. Ignoruję...\n";
		return false;
	}

	// rozmiar fragmentu się nie zgadza
	if ( (chunk_data.size() != chunk_size) && (chunk < chunks_count-1) ) {
		mutex.v();
		sw << "Rozmiar fragmentu się nie zgadza. Ignoruję...\n";
		return false;
	}

	chunks.push_back(chunk);
	chunks_done++;

	locked.remove(seed_id);

	// zapisywanie fragmentu pliku
	QFile filepart(QString("./downloads/") + fname + ".part" + QString::number(chunk));
	filepart.open(QFile::WriteOnly);
	filepart.write(chunk_data.data(), chunk_data.size());
	filepart.close();

	if (chunks_done >= chunks_count) {
		//sw << "Pobrano ostatni fragment pliku. Obliczam sume kontrolną... ";
		status = jsFinished;

		merge(fname);

		QByteArray md5 = Utils::md5sum(QString("./downloads/") + fname);
		if (md5 != hash) {
			/// @todo usunąć plik z folderu
			sw << "Pliki się różnią. Usuwam pobrany plik.\n";
		} else {
			/// @todo dodać plik do udostępniania
			sw << "Plik pobrano bez błędów.\n";
		}
	}

	mutex.v();

	return true;
}*/

bool Job::merge(QString fname) {
	QFile out_file(QString("./downloads/") + fname);
	out_file.open(QFile::WriteOnly);

	QByteArray buf;

	QFile part_file;
	for (int i = 0; i < chunks_count; ++i) {
		part_file.setFileName(QString("./downloads/") + fname + ".part" + QString::number(i));
		part_file.open(QFile::ReadOnly);

		buf = part_file.read(chunk_size);

		part_file.close();

		out_file.write(buf.data(), chunk_size);
	}

	out_file.close();

	return true;
}

void Job::freeSeed(int client_id, bool remove) {
	mutex.p();

	for (int i = 0; i < seeds.size(); ++i) {
		if (seeds[i].id == client_id) {
			seeds[i].connected = false;
			if (remove) {
				std::cout << "Usunięto klienta " << seeds[i].id << ":" << seeds[i].ip << ":" << seeds[i].port << "\n";
				seeds.removeAt(i);
			}
		}
	}

	if (locked.contains(client_id)) {
		locked.remove(client_id);
		seeds_count--;
	}

	mutex.v();
}

void Job::addSeed(PeerInfo peer) {
	mutex.p();

	for (int i = 0; i < seeds.size(); ++i)
		if (seeds[i].id == peer.id) {
			mutex.v();
			return;
		}

	seeds.push_back(peer);
	std::cout << "Dodano klienta " << peer.id << ":" << peer.ip << ":" << peer.port << "\n";

	mutex.v();
}

void Job::lockChunk(int id, int client) {
	locked.insert(client, id);
}

int Job::lockCount() {
	return locked.size();
}

} // namespace Client
